//----------------------------------------------------------------------------//
//                                                                            //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation  //
// and distributed under the MIT License (MIT).                               //
//                                                                            //
// Copyright (c) Guillaume Blanc                                              //
//                                                                            //
// Permission is hereby granted, free of charge, to any person obtaining a    //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation  //
// the rights to use, copy, modify, merge, publish, distribute, sublicense,   //
// and/or sell copies of the Software, and to permit persons to whom the      //
// Software is furnished to do so, subject to the following conditions:       //
//                                                                            //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software.                        //
//                                                                            //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING    //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER        //
// DEALINGS IN THE SOFTWARE.                                                  //
//                                                                            //
//----------------------------------------------------------------------------//

#include "SkrAnimTool/ozz/track_builder.h"

#include <cassert>
#include <cmath>
#include <cstring>
#include <limits>

#include "SkrAnimTool/ozz/raw_track.h"
#include "SkrAnim/ozz/track.h"
#include "SkrAnim/ozz/base/memory/allocator.h"

namespace ozz
{
namespace animation
{
namespace offline
{

namespace
{

template <typename _RawTrack>
void PatchBeginEndKeys(const _RawTrack& _input, typename _RawTrack::Keyframes* keyframes)
{
    if (_input.keyframes.empty())
    {
        // Empty (identity) is supported during sampling.
    }
    else if (_input.keyframes.size() < 2)
    { // Constant
        const typename _RawTrack::Keyframe& src_key = _input.keyframes.front();
        const typename _RawTrack::Keyframe  begin   = { RawTrackInterpolation::kLinear,
                                                        0.f, src_key.value };
        keyframes->push_back(begin);
    }
    else
    {
        // Copy all source data.
        // Push an initial and last keys if they don't exist.
        if (_input.keyframes.front().ratio != 0.f)
        {
            const typename _RawTrack::Keyframe& src_key = _input.keyframes.front();
            const typename _RawTrack::Keyframe  begin   = {
                RawTrackInterpolation::kLinear, 0.f, src_key.value
            };
            keyframes->push_back(begin);
        }
        for (size_t i = 0; i < _input.keyframes.size(); ++i)
        {
            keyframes->push_back(_input.keyframes[i]);
        }
        if (_input.keyframes.back().ratio != 1.f)
        {
            const typename _RawTrack::Keyframe& src_key = _input.keyframes.back();
            const typename _RawTrack::Keyframe  end     = { RawTrackInterpolation::kLinear,
                                                            1.f, src_key.value };
            keyframes->push_back(end);
        }
    }
}

template <typename _Keyframes>
void Fixup(_Keyframes* _keyframes)
{
    // Nothing to do by default.
    (void)_keyframes;
}
} // namespace

// Ensures _input's validity and allocates _animation.
// An animation needs to have at least two key frames per joint, the first at
// t = 0 and the last at t = 1. If at least one of those keys are not
// in the RawAnimation then the builder creates it.
template <typename _RawTrack, typename _Track>
unique_ptr<_Track> TrackBuilder::Build(const _RawTrack& _input) const
{
    // Tests _raw_animation validity.
    if (!_input.Validate())
    {
        return unique_ptr<_Track>();
    }

    // Everything is fine, allocates and fills the animation.
    // Nothing can fail now.
    unique_ptr<_Track> track = make_unique<_Track>();

    // Copy data to temporary prepared data structure
    typename _RawTrack::Keyframes keyframes;
    // Guessing a worst size to avoid realloc.
    const size_t worst_size =
        _input.keyframes.size() * 2 + // * 2 in case all keys are kStep
        2;                            // + 2 for first and last keys
    keyframes.reserve(worst_size);

    // Ensure there's a key frame at the start and end of the track (required for
    // sampling).
    PatchBeginEndKeys(_input, &keyframes);

    // Fixup values, ex: successive opposite quaternions that would fail to take
    // the shortest path during the normalized-lerp.
    Fixup(&keyframes);

    // Allocates output track.
    const size_t name_len = _input.name.size();
    track->Allocate(keyframes.size(), _input.name.size());

    // Copy all keys to output.
    assert(keyframes.size() == track->ratios_.size() && keyframes.size() == track->values_.size() && keyframes.size() <= track->steps_.size() * 8);
    memset(track->steps_.data(), 0, track->steps_.size_bytes());
    for (size_t i = 0; i < keyframes.size(); ++i)
    {
        const typename _RawTrack::Keyframe& src_key = keyframes[i];
        track->ratios_[i]                           = src_key.ratio;
        track->values_[i]                           = src_key.value;
        track->steps_[i / 8] |=
            (src_key.interpolation == RawTrackInterpolation::kStep) << (i & 7);
    }

    // Copy track's name.
    if (name_len)
    {
        strcpy(track->name_, _input.name.c_str());
    }

    return track; // Success.
}

unique_ptr<FloatTrack> TrackBuilder::operator()(
    const RawFloatTrack& _input
) const
{
    return Build<RawFloatTrack, FloatTrack>(_input);
}
unique_ptr<Float2Track> TrackBuilder::operator()(
    const RawFloat2Track& _input
) const
{
    return Build<RawFloat2Track, Float2Track>(_input);
}
unique_ptr<Float3Track> TrackBuilder::operator()(
    const RawFloat3Track& _input
) const
{
    return Build<RawFloat3Track, Float3Track>(_input);
}
unique_ptr<Float4Track> TrackBuilder::operator()(
    const RawFloat4Track& _input
) const
{
    return Build<RawFloat4Track, Float4Track>(_input);
}

namespace
{
// Fixes-up successive opposite quaternions that would fail to take the shortest
// path during the lerp.
template <>
void Fixup<RawQuaternionTrack::Keyframes>(
    RawQuaternionTrack::Keyframes* _keyframes
)
{
    const math::Quaternion identity = math::Quaternion::identity();
    for (size_t i = 0; i < _keyframes->size(); ++i)
    {
        RawQuaternionTrack::ValueType& src_key = _keyframes->at(i).value;

        // Normalizes input quaternion.
        src_key = NormalizeSafe(src_key, identity);

        // Ensures quaternions are all on the same hemisphere.
        if (i == 0)
        {
            if (src_key.w < 0.f)
            {
                src_key = -src_key; // Q and -Q are the same rotation.
            }
        }
        else
        {
            RawQuaternionTrack::ValueType& prev_key = _keyframes->at(i - 1).value;
            const float                    dot      = src_key.x * prev_key.x + src_key.y * prev_key.y +
                              src_key.z * prev_key.z + src_key.w * prev_key.w;
            if (dot < 0.f)
            {
                src_key = -src_key; // Q an -Q are the same rotation.
            }
        }
    }
}
} // namespace

unique_ptr<QuaternionTrack> TrackBuilder::operator()(
    const RawQuaternionTrack& _input
) const
{
    return Build<RawQuaternionTrack, QuaternionTrack>(_input);
}
} // namespace offline
} // namespace animation
} // namespace ozz
